#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "els_vm.h"
#include "els_vm_tree.h"
#include "unit.h"
#include "els_vm_bytecode.h"
#include "inout.h"
#include "parser.h"

#define MINBUFF 260
#define els_lex_next() { current = input(); }
#define save(x) { *yytextLast++ = (x); }
#define save_and_next()  { save(current); els_lex_next(); }
#define KEYWORDSIZE (sizeof(keyword)/sizeof(keyword[0]))
#define THIS 800

static int els_yylex_ptr = 0;
static int els_yylex_sen_ptr = 0;
static unsigned int current;
static char *yytext = NULL;
static int textsize = 0;
static char *yytextLast;
static Input input;
static char els_yytmp_class[1024];

void els_setinput (Input fn){
    current = ' ';
    input = fn;
    if (yytext == NULL){
        textsize = MINBUFF;
        yytext = newvector(textsize, char);
    }
}
char *els_lasttext (void){
    *yytextLast = 0;
    return yytext;
}
static const struct{
    char *string;
    int key;
} keyword[] = {
    {"&&", AND},
    {"且", AND},
    {"时", DO},
    {"否则", ELSE},
    {"若", ELIF},
    {"另外", ELIF},
    {";", END},
    {"方法", FC},
    {"如果", IF},
    {"令", LET},
    {"无", KEY_NULL},
    {"空", KEY_NULL},
    {"伪", KEY_NULL},
    {"!", NOT},
    {"非", NOT},
    {"||", OR},
    {"或", OR},
    {"重复", LOOP},
    {"回", RETURN},
    {"则", SO},
    {"至", TO},
    {"当", WITH},
    {"do", DO},
    {"else", ELSE},
    {"elif", ELIF},
    {"def", FC},
    {"if", IF},
    {"var", LET},
    {"null", KEY_NULL},
    {"loop", LOOP},
    {"rt", RETURN},
    {"so", SO},
    {"to", TO},
    {"or", OR},
    {"and", AND},
    {"with", WITH},
    {"此", THIS},
};

static int FindKeyWord (char *name){
    int l = 0;
    int h = KEYWORDSIZE - 1;
    while (l <= h){
        if (!strcmp(name, keyword[l].string)) return keyword[l].key;
        l = l+1;
    }
    return 0;
}

static void growtext (void){
    int size = yytextLast - yytext;
    textsize *= 2;
    yytext = growvector(yytext, textsize, char);
    yytextLast = yytext + size;
}

static int read_long_string (void){
    int cont = 0;
    int spaceleft = textsize - (yytextLast - yytext);
    save(current);
    while (1){
        if (spaceleft <= 2){
            growtext();
            spaceleft = textsize - (yytextLast - yytext);
        }
        switch (current){
            case EOF:
            case 0:return ERRORTOKEN;
            case '"':
            save(current);
                return STRING;
            case '\n':
                els_linenumber++; 
            default:
                save_and_next(); spaceleft--;
        }
    }
}


int yylex (void){
    float a;
    static int linelasttoken = 0;
    /*
    if(els_yylex_ptr==1){
        
    }
    if(els_yylex_ptr==2){
        els_yylex_ptr = 0;
        yylval.pNode = els_lexgetstring(yytextLast);
        return NAME;
    }
    */
    switch (els_yylex_ptr){
        case 0: 
            break;
        case 1:
            els_yylex_ptr = 0;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case 2:
            els_yylex_ptr = 0;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case 3:
            els_yylex_ptr = 0;
            yylval.vFloat = 1;
            return NUMBER;
        case 4:
            els_yylex_ptr = 3;
            return '+';
        case -4:
             els_yylex_ptr = 3;
            return '-';
        case 5:
            els_yylex_ptr = 4;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case 6:
            els_yylex_ptr = -4;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case 7:
            els_yylex_ptr = -7;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case -7:
            els_yylex_ptr = 0;
            return '+';
        case 8:
            els_yylex_ptr = -8;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case -8:
            els_yylex_ptr = 0;
            return '-';
        case 9:
            els_yylex_ptr = -9;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case -9:
            els_yylex_ptr = 0;
            return CONC;
        case 10:
            els_yylex_ptr = -10;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case -10:
            els_yylex_ptr = 2;
            return '*';
        case 11:
            els_yylex_ptr = -11;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case -11:
            els_yylex_ptr = 0;
            return '*';
        case 12:
            els_yylex_ptr = -12;
            yylval.pNode = els_lexgetstring(yytextLast);
            return NAME;
        case -12:
            els_yylex_ptr = 0;
            return '/';
    default:
        break;
    }
    if (els_debug) els_vm_codedebugline(linelasttoken);
    linelasttoken = els_linenumber;
    
    while (1){
        yytextLast = yytext;
        switch (current){
            case EOF:
            case 0:
                return 0;
            case '\n': linelasttoken = ++els_linenumber;
            case ' ':
            case '\r':  
            case '\t':
                els_lex_next();
                continue;
            case '/':
                els_lex_next();
                if(current=='='){
                    els_lex_next();
                    els_yylex_ptr = 12;
                    return '=';
                }else return '/'; 
            case '#':
                do { els_lex_next(); } while (current != '\n' && current != 0&&current != EOF);
                continue;
            case '[':
                save_and_next();
                if (current != '[') return '[';
            case '=':
                save_and_next();
                if (current != '=') return '=';
                else { save_and_next(); return EQ; }
            case '*':
                els_lex_next();
                if(current=='*'){
                    els_lex_next();
                    els_yylex_ptr = 10;
                    return '=';
                }else if(current=='='){
                    els_lex_next();
                    els_yylex_ptr = 11;
                    return '=';
                }else return '*';
            case '-':
                els_lex_next();
                if(current=='-'){
                    els_lex_next();
                    els_yylex_ptr = 6;
                    return '=';
                }else if(current=='='){
                    els_lex_next();
                    els_yylex_ptr = 8;
                    return '=';
                }else return '-';
            case '+':
                els_lex_next();
                if(current=='+'){
                    els_lex_next();
                    els_yylex_ptr = 5;
                    return '=';
                }else if(current=='='){
                    els_lex_next();
                    els_yylex_ptr = 7;
                    return '=';
                }else return '+';
            case '<':
                save_and_next();
                if (current != '=') return '<';
                else { save_and_next(); return LE; }
            case '>':
                save_and_next();
                if (current != '=') return '>';
                else { save_and_next(); return GE; }
            case '|':
                save_and_next();
                if (current != '|') return '|';
                else{save_and_next();return OR;}
            case '!':
                save_and_next();
                if (current != '=') return NOT;
                else { save_and_next(); return NE; }
            case '"':{
                    save_and_next(); 
                    if (read_long_string() == ERRORTOKEN)
                    return ERRORTOKEN;
                    save_and_next();  
                    *(yytextLast-2) = 0; 
                    yylval.vWord = els_vm_findconstantbyname(yytext+2);
                    return STRING;
                }
            case '\'':{
                int del = current;
                int spaceleft = textsize - (yytextLast - yytext);
                els_lex_next();
                while (current != del){
                    if (spaceleft <= 2) {
                        growtext();
                        spaceleft = textsize - (yytextLast - yytext);
                    } 
                    spaceleft--;
                    switch (current){
                        case EOF:
                        case 0:
                        case '\r':
                            save('\r');
                            els_lex_next();break;
                        case '\n':
                            save('\n'); els_lex_next();break;
                            //return ERRORTOKEN;
                        case '\\':
                            els_lex_next();  
                            switch (current){
                                case 'n': save('\n'); els_lex_next(); break;
                                case 't': save('\t'); els_lex_next(); break;
                                case 'r': save('\r'); els_lex_next(); break;
                                case 'a': save('\a'); els_lex_next(); break;
                                case 'b': save('\b'); els_lex_next(); break;
                                case 'v': save('\v'); els_lex_next(); break;
                                default : save(current); els_lex_next(); break;
                            }
                            break;
                        default:
                            save_and_next();
                    }
                }       
                els_lex_next();  
                *yytextLast = 0;
                yylval.vWord = els_vm_findconstantbyname(yytext);
                return STRING;
            }
            case ':':{
                els_lex_next();
                if(current==':'){ 
                    els_lex_next();
                    return ':';
                }else if(current=='('){
                    els_yylex_ptr = 1;
                    return ':';
                }else if(els_yylex_sen_ptr){
                    switch(els_yylex_sen_ptr){
                        case 1:
                            els_yylex_sen_ptr = 0;
                            return SO;
                        case 2:
                            els_yylex_sen_ptr = 0;
                            return DO;
                        case 3:
                            els_yylex_sen_ptr =0;
                            break;
                        default: 
                    }
                }
            }
            case '$':{
                els_lex_next();
                els_yylex_ptr =2;
                return LET;
            }
            /*case 'a': case 'b': case 'c': case 'd': case 'e':
            case 'f': case 'g': case 'h': case 'i': case 'j':
            case 'k': case 'l': case 'm': case 'n': case 'o':
            case 'p': case 'q': case 'r': case 's': case 't':
            case 'u': case 'v': case 'w': case 'x': case 'y':
            case 'z':
            case 'A': case 'B': case 'C': case 'D': case 'E':
            case 'F': case 'G': case 'H': case 'I': case 'J':
            case 'K': case 'L': case 'M': case 'N': case 'O':
            case 'P': case 'Q': case 'R': case 'S': case 'T':
            case 'U': case 'V': case 'W': case 'X': case 'Y':
            case 'Z': 
            case '_':{
                Word res;
                do { save_and_next(); } while (isname(current));
                *yytextLast = 0;
                res = FindKeyWord(yytext);
                if(res==THIS){
                    yylval.pNode = els_lexgetstring("此");
                    return NAME;
                }
                if (res) return res;
                yylval.pNode = els_lexgetstring(yytext);
                return NAME;
            }*/
            case '&': els_lex_next();
                if(current=='='){
                    els_lex_next();
                    els_yylex_ptr = 9;
                    return '=';
                }else if (current != '&') return CONC;
                else {els_lex_next();return AND;}
            case '.':
                save_and_next();
                if (!isdigit(current)) return '.';
	            a=0.0;
                goto fraction;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
	            a=0.0;
                do { a=10*a+current-'0'; save_and_next(); } while (isdigit(current));
                if (current == '.') save_and_next();
            fraction:{ 
                float da=0.1;
	            while (isdigit(current)){
                    a+=(current-'0')*da; da/=10.0; save_and_next()
                };
                if (current == 'e' || current == 'E'){
	                int e=0;
	                int neg;
	                float ea;
                    save_and_next();
	                neg=(current=='-');
                    if (current == '+' || current == '-') save_and_next();
                    if (!isdigit(current)) return ERRORTOKEN;
                    do { e=10*e+current-'0'; save_and_next(); } while (isdigit(current));
	                for (ea=neg?0.1:10.0; e>0; e>>=1) {
                        if (e & 1) a*=ea;
	                    ea*=ea;
	                }
                }
                yylval.vFloat = a;
                return NUMBER;
            }
            
            default:{
                if(isname(current)){
                    Word res;
                    do { save_and_next(); } while (isname(current));
                    *yytextLast = 0;
                    res = FindKeyWord(yytext);
                    switch(res){
                        case 0: break;
                        case THIS:
                            yylval.pNode = els_lexgetstring("this");
                            return NAME;
                        case IF: case ELIF: 
                            els_yylex_sen_ptr = 1;
                            return res;
                        case WITH: 
                            els_yylex_sen_ptr = 2;
                            return res;
                        case ELSE:
                            els_yylex_sen_ptr = 3;
                            return res;
                        default: return res;
                    }
                    yylval.pNode = els_lexgetstring(yytext);
                    return NAME;
                }
                unsigned int tmp = current;
                save_and_next();
                return tmp;
            }
        }
    }
}
int isname( int cr){
    if(isalnum(cr)||cr=='_'||cr==';'||cr>127||cr<0) return 1;
    else    return 0;
}